// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2016-2019, LomboTech Co.Ltd.
 * Authors:
 *	lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include <linux/suspend.h>
#include "lombo_dc_blk.h"
#include "lombo_dc_priv.h"
#include "lombo_dc_win.h"

#define DC_MAX_WIDTH     8192
#define DC_MAX_HEIGHT    8192

static LIST_HEAD(dc_list);

struct lombo_dc *lombo_find_dc_by_id(int id)
{
	struct lombo_dc *dc;

	list_for_each_entry(dc, &dc_list, list) {
		if (dc->id == id)
			return dc;
	}

	return ERR_PTR(-EPROBE_DEFER);
}

/* private api for dc_win */
int lombo_dc_convert_drm_format(int format)
{
	switch (format) {
	case DRM_FORMAT_ARGB8888:
	case DRM_FORMAT_XRGB8888:
		return DPU_FMT_ARGB8888;
	case DRM_FORMAT_ABGR8888:
	case DRM_FORMAT_XBGR8888:
		return DPU_FMT_ABGR8888;
	case DRM_FORMAT_RGBA8888:
	case DRM_FORMAT_RGBX8888:
		return DPU_FMT_RGBA8888;
	case DRM_FORMAT_BGRA8888:
	case DRM_FORMAT_BGRX8888:
		return DPU_FMT_BGRA8888;
	case DRM_FORMAT_RGB888:
		return DPU_FMT_RGB888;
	case DRM_FORMAT_BGR888:
		return DPU_FMT_BGR888;
	case DRM_FORMAT_RGB565:
		return DPU_FMT_RGB565;
	case DRM_FORMAT_BGR565:
		return DPU_FMT_BGR565;
	case DRM_FORMAT_ARGB1555:
	case DRM_FORMAT_XRGB1555:
		return DPU_FMT_ARGB1555;
	case DRM_FORMAT_ABGR1555:
	case DRM_FORMAT_XBGR1555:
		return DPU_FMT_ABGR1555;
	case DRM_FORMAT_RGBA5551:
	case DRM_FORMAT_RGBX5551:
		return DPU_FMT_RGBA5551;
	case DRM_FORMAT_BGRA5551:
	case DRM_FORMAT_BGRX5551:
		return DPU_FMT_BGRA5551;
	case DRM_FORMAT_ARGB4444:
	case DRM_FORMAT_XRGB4444:
		return DPU_FMT_ARGB4444;
	case DRM_FORMAT_ABGR4444:
	case DRM_FORMAT_XBGR4444:
		return DPU_FMT_ABGR4444;
	case DRM_FORMAT_RGBA4444:
	case DRM_FORMAT_RGBX4444:
		return DPU_FMT_RGBA4444;
	case DRM_FORMAT_BGRA4444:
	case DRM_FORMAT_BGRX4444:
		return DPU_FMT_BGRA4444;

	case DRM_FORMAT_NV12:
		return DPU_FMT_YUV420SP;
	case DRM_FORMAT_NV21:
		return DPU_FMT_YVU420SP;
	case DRM_FORMAT_NV16:
		return DPU_FMT_YUV422SP;
	case DRM_FORMAT_NV61:
		return DPU_FMT_YVU422SP;
	case DRM_FORMAT_AYUV:
		return DPU_FMT_AYUV;

	default:
		DRM_ERROR("unsupport format[%.4s]\n", (char *)&format);
		return 0;
	}
}

/* get out csfmt of dc */
int lombo_dc_get_csfmt(struct lombo_dc *dc)
{
	LOMBO_DC_ASSERT(dc);

	switch (dc->scn_fmt) {
	case DPU_FMT_YUV444P:
		return LOMBO_CSFMT_YUV;
	case DPU_FMT_RGB888:
		return LOMBO_CSFMT_RGB;
	default:
		DRM_WARN("unknown scn_fmt %d of dc%d\n",
			 dc->scn_fmt, dc->id);
		return LOMBO_CSFMT_RGB;
	}
}

/* get dc output resolution */
int lombo_dc_get_scn(struct lombo_dc *dc, int *width, int *height)
{
	LOMBO_DC_ASSERT(dc);

	if (width)
		*width = dc->scn_w;
	if (height)
		*height = dc->scn_h;

	return 0;
}

int lombo_dc_atomic_check(struct lombo_dc *dc,
	struct drm_crtc_state *state)
{
	struct drm_display_mode *mode = &state->mode;

	LOMBO_DC_ASSERT(dc);

	DRM_DEBUG_ATOMIC("scn[%ux%u]\n", mode->hdisplay, mode->vdisplay);
	dc->scn_w = mode->hdisplay;
	dc->scn_h = mode->vdisplay;
	dc->scn_fmt = DPU_FMT_RGB888;

	return 0;
}

static int lombo_dc_resume(struct device *dev, int sw)
{
	struct lombo_dc *dc = dev_get_drvdata(dev);

	return lombo_dpu_clk_prepare_enable(dc->clk, sw);
}

static int lombo_dc_suspend(struct device *dev)
{
	struct lombo_dc *dc = dev_get_drvdata(dev);

	return lombo_dpu_clk_disable_unprepare(dc->clk);
}

void lombo_dc_mode_set_nofb(struct lombo_dc *dc,
	struct drm_display_mode *mode)
{
}

void lombo_dc_atomic_enable(struct lombo_dc *dc)
{
	dpu_color_t bk_color = {
		.b = 0,
		.g = 0,
		.r = 0,
		.a = 0xFF,
	};
	dc_enhance_info_t *ce = &dc->color.ce;
	struct lombo_crtc *ecrtc = dc->ecrtc;

	LOMBO_DC_ASSERT(dc);

	lombo_dc_resume(dc->dev, 0);
	if (lombo_disp_boot_sync_id(dc->drm->dev_private, "dc", dc->id))
		return;

	csp_dc_init(dc->id);
	if ((dc->scn_w == 0) ||  (dc->scn_w > DC_MAX_WIDTH) ||
	    (dc->scn_h == 0) ||  (dc->scn_h > DC_MAX_HEIGHT))
		DRM_ERROR("invalid screen size[%dx%d]\n", dc->scn_w, dc->scn_h);
	csp_dc_set_screen_size(dc->id, dc->scn_w, dc->scn_h);
	csp_dc_set_output_mode(dc->id,
			       ecrtc->is_interlace ? 1 : 0,
			       ecrtc->field_pol ? 1 : 0);

	if (dc->scn_fmt == DPU_FMT_YUV444P) {
		bk_color.g = 0x80; /* u */
		bk_color.b = 0x80; /* v */
	}
	csp_dc_set_backcolor(dc->id, &bk_color);

	ce->fmt = dc->scn_fmt; /* configure the output data format of dc. */
	csp_dc_set_enhancement(dc->id, ce);
	csp_dc_cm_set_coef(dc->id, dc->color.cm.cm_curve);
	if (dc->color.color_enable)
		csp_dc_cm_set_enable(dc->id);
	else
		csp_dc_cm_set_disable(dc->id);

	csp_dc_enable(dc->id);
	csp_dc_load_dbr(dc->id);
}

void lombo_dc_atomic_disable(struct lombo_dc *dc)
{
	LOMBO_DC_ASSERT(dc);
	csp_dc_disable(dc->id);
	csp_dc_load_dbr(dc->id);
	lombo_dc_suspend(dc->dev);
}

void lombo_dc_set_crtc_tcon(struct lombo_dc *dc,
	struct lombo_crtc *ecrtc, struct lombo_tcon *tcon)
{
	LOMBO_DC_ASSERT(dc);
	dc->tcon = tcon;
	dc->ecrtc = ecrtc;
}

struct drm_plane **lombo_dc_planes_init(struct lombo_dc *dc)
{
	struct lombo_drv *drv;
	struct lombo_boot_disp *bd;
	struct drm_plane **planes;
	uint8_t primary_win = 0;
	int i;

	LOMBO_DC_ASSERT(dc);
	LOMBO_DC_ASSERT(dc->cfg->win_num);
	DRM_DEV_DEBUG_DRIVER(dc->dev, "\n");

	planes = devm_kcalloc(dc->dev,
			      dc->cfg->win_num + 2,
			      sizeof(*planes),
			      GFP_KERNEL);
	if (!planes)
		return ERR_PTR(-ENOMEM);

	drv = dc->drm->dev_private;
	bd = drv->boot_disp;
	if (bd && (bd->dc_id == dc->id))
		primary_win = bd->win_id;
	else if (dc->id < ARRAY_SIZE(drv->fb_win_id))
		primary_win = drv->fb_win_id[dc->id];
	else
		DRM_DEV_INFO(dc->dev, "no primary win\n");
	LOMBO_DC_ASSERT(primary_win < dc->cfg->win_num);
	for (i = 0; i < dc->cfg->win_num; i++) {
		struct lombo_dc_win *win;
		enum drm_plane_type type = DRM_PLANE_TYPE_OVERLAY;

		if (i == primary_win)
			type = DRM_PLANE_TYPE_PRIMARY;
		win = lombo_dc_win_init(dc, i, type);
		if (IS_ERR(win)) {
			DRM_DEV_ERROR(dc->dev, "Couldn't init win%d\n", i);
			return ERR_CAST(win);
		}

		planes[i] = &win->base;
	}

	dc->blk = lombo_dc_blk_init(dc, DRM_PLANE_TYPE_CURSOR, i);
	if (!IS_ERR_OR_NULL(dc->blk)) {
		planes[i] = &dc->blk->bl_array[0].base;
	} else {
		DRM_ERROR("blk init fail\n");
		return ERR_CAST(dc->blk);
	}
	dc->planes = planes;

	return planes;
}

int lombo_dc_probe_fbdev(struct lombo_dc *dc, struct drm_fb_helper *fb_helper)
{
	struct lombo_dc_fbdev_info *fbdev;
	struct drm_framebuffer *fb;
	const uint32_t pitch_align = 16;
	uint32_t width, height, pitch;
	uint32_t num = 1;
	uint32_t i;
	uint32_t ret = 0;

	if (!dc || !fb_helper || !fb_helper->fb) {
		DRM_WARN("null pointer\n");
		return -EINVAL;
	}

	fbdev = &dc->actual_fbdev;
	fb = fb_helper->fb;

	if (!fb->format || fb->format->num_planes != 1) {
		DRM_WARN("invalid fb format\n");
		return -EINVAL;
	}

	width = fb->width;
	height = fb->height;
	if (fb_helper->sw_rotations == DRM_MODE_ROTATE_180) {
		fbdev->rotate = LOMBO_DRM_TRANSFORM_ROT_180;
	} else if (fb_helper->sw_rotations == DRM_MODE_ROTATE_90) {
		fbdev->rotate = LOMBO_DRM_TRANSFORM_ROT_270; /* no doubt */
		swap(width, height);
	} else if (fb_helper->sw_rotations == DRM_MODE_ROTATE_270) {
		fbdev->rotate = LOMBO_DRM_TRANSFORM_ROT_90; /* no doubt */
		swap(width, height);
	} else {
		DRM_WARN("invalid sw_rot %d\n", fb_helper->sw_rotations);
		return -EINVAL;
	}
	pitch = ALIGN(width * fb->format->cpp[0], pitch_align);

	#ifdef CONFIG_DRM_FBDEV_OVERALLOC
	num *= CONFIG_DRM_FBDEV_OVERALLOC;
	num /= 100;
	#endif
	if (num > ARRAY_SIZE(fbdev->fbs))
		num = ARRAY_SIZE(fbdev->fbs);

	for (i = 0; i < num; i++) {
		struct drm_fb_helper_surface_size sizes;
		struct drm_framebuffer *f;
		struct drm_gem_cma_object *cma_obj;

		cma_obj = drm_gem_cma_create(dc->drm, pitch * height);
		if (IS_ERR_OR_NULL(cma_obj)) {
			ret = PTR_ERR(cma_obj);
			DRM_ERROR("cma obj create failed, ret=%d\n", ret);
			goto fbs_free;
		}

		sizes.fb_width = sizes.surface_width = width;
		sizes.fb_height = sizes.surface_height = height;
		sizes.surface_bpp = fb->format->cpp[0] * 8;
		sizes.surface_depth = sizes.surface_bpp;
		f = drm_gem_fbdev_fb_create(dc->drm, &sizes, pitch_align,
					    &cma_obj->base, NULL);
		if (IS_ERR_OR_NULL(f)) {
			ret = PTR_ERR(f);
			DRM_ERROR("fb create failed, ret=%d\n", ret);
			goto fbs_free;
		}
		if (f->format != fb->format)
			DRM_WARN("0x%x can not match fb format\n",
				f->format->format);

		fbdev->fbs[i] = f;
	}

	fbdev->num = num;
	fbdev->fb_cur = num - 1;
	fbdev->vfb = fb;
	fb = fbdev->fbs[0];
	fbdev->format = fb->format;
	fbdev->width = fb->width;
	fbdev->height = fb->height;
	fbdev->pitch = fb->pitches[0];

	return 0;

fbs_free:
	for (i = 0; i < num; i++) {
		if (!fbdev->fbs[i])
			continue;
		drm_framebuffer_unreference(fbdev->fbs[i]);
		fbdev->fbs[i] = NULL;
	}

	fbdev->rotate = 0;

	return ret;
}

void lombo_dc_color_pre_commit(struct lombo_dc *dc)
{
	if (dc->color.ce_info_changed || dc->color.cm_curve_changed)
		dc->color.color_commit = true;
	if (dc->color.cm_curve_changed)
		csp_dc_cm_generate_saturation_curve(dc->color.cm.cm_curve,
			2 * dc->color.cm.sat_red - 100,
			2 * dc->color.cm.sat_orange - 100,
			2 * dc->color.cm.sat_yellow - 100,
			2 * dc->color.cm.sat_green - 100,
			2 * dc->color.cm.sat_cyan - 100,
			2 * dc->color.cm.sat_blue - 100,
			2 * dc->color.cm.sat_purple - 100,
			2 * dc->color.cm.sat_magenta - 100);
}

static int lombo_dc_color_commit(struct lombo_dc *dc)
{
	int ret = 0;

	if (dc->color.color_commit) {
		dc->color.color_commit = false;
		if (dc->color.ce_info_changed) {
			ret = csp_dc_set_enhancement(dc->id, &dc->color.ce);
			if (ret)
				DRM_ERROR("csp set ce err ret[%d]\n", ret);

		}
		if (dc->color.cm_curve_changed) {
			dc->color.cm_curve_changed = false;
			ret = csp_dc_cm_set_coef(dc->id, dc->color.cm.cm_curve);
			if (ret)
				DRM_ERROR("csp set cm err ret[%d]\n", ret);
		}
		if (dc->color.color_enable)
			csp_dc_cm_set_enable(dc->id);
		else
			csp_dc_cm_set_disable(dc->id);
	}

	return ret;
}

int lombo_dc_commit(struct lombo_dc *dc)
{
	int i;

	LOMBO_DC_ASSERT(dc);
	lombo_dc_blk_commit(dc->blk);

	for (i = 0; i < dc->cfg->win_num; i++)
		lombo_dc_win_commit(to_lombo_dc_win(dc->planes[i]));
	lombo_dc_color_commit(dc);
	csp_dc_load_dbr(dc->id);

	schedule_work(&dc->page_flip_work);

	return 0;
}

static void lombo_dc_page_flip_worker(struct work_struct *work)
{
	struct lombo_dc *dc = container_of(work,
					   struct lombo_dc,
					   page_flip_work);
	int i;

	for (i = 0; i < dc->cfg->win_num; i++)
		lombo_dc_win_page_flip_worker(to_lombo_dc_win(dc->planes[i]));
}

static char *lombo_dc_get_fmt_str(dpu_pixel_format_t fmt)
{
	switch (fmt) {
	case DPU_FMT_RGB888:
		return "RG24";
	case DPU_FMT_YUV444P:
		return "YU24";
	default:
		DRM_ERROR("unknown fmt 0x%x\n", fmt);
		return "invl";
	}
}

int lombo_dc_kms_show(struct lombo_dc *dc,
	struct seq_file *m, void *data)
{
	int i;
	struct drm_format_name_buf fmt_buf;

	seq_printf(m, "DC(%d): outsize(%dx%d), outfmt(%s), clk=%luHz\n",
		   dc->id, dc->scn_w, dc->scn_h,
		   lombo_dc_get_fmt_str(dc->scn_fmt),
		   lombo_dpu_clk_get_real_rate(dc->clk));

	seq_puts(m, "win fmt     W x H    offset_addr[0x]        pitch[0x]       source(x, y, w, h)   screen(x, y, w, h)  alpha\n");
	seq_puts(m, "---|----|-----------|-----------------|--------------------|--------------------|--------------------|-----\n");
	for (i = 0; i < dc->cfg->win_num; i++) {
		struct lombo_dc_win *win = to_lombo_dc_win(dc->planes[i]);
		struct lombo_fb_cfg *fb_cfg = &win->fb_cfg;
		struct drm_framebuffer *fb = fb_cfg->fb;

		if (!fb) {
			continue;
		} else if (fb->modifier) {
			seq_printf(m, "win%d fb is modifier=0x%llx\n",
				   win->id, fb->modifier);
			continue;
		}

		drm_get_format_name(fb->format->format, &fmt_buf);
		fmt_buf.str[4] = 0;
		seq_printf(m, "%d   %s  %4dx%-4d [%02x,%06x,%06x][%6x,%6x,%6x](%4d,%4d,%4d,%4d)(%4d,%4d,%4d,%4d)(0x%04x)\n",
			   win->id, fmt_buf.str, fb->width, fb->height,
			   fb->offsets[0], fb->offsets[1], fb->offsets[2],
			   fb->pitches[0], fb->pitches[1], fb->pitches[2],
			   fb_cfg->src_x >> 16, fb_cfg->src_y >> 16,
			   fb_cfg->src_w >> 16, fb_cfg->src_h >> 16,
			   fb_cfg->crtc_x, fb_cfg->crtc_y,
			   fb_cfg->crtc_w, fb_cfg->crtc_h,
			   win->alpha);
	}

	return 0;
}

int lombo_dc_get_color_status(struct lombo_dc *dc, int type)
{
	int ret = 0;

	switch (type) {
	case LOMBO_COLOR_ENABLE:
		ret = dc->color.color_enable;
		break;
	case LOMBO_CE_BRIGHTNESS:
		ret = (int)dc->color.ce.brightness;
		break;
	case LOMBO_CE_SATURATION:
		ret = !dc->cfg->has_cm ?
			(int)dc->color.ce.saturation : -ENODEV;
		break;
	case LOMBO_CE_CONTRAST:
		ret = (int)dc->color.ce.contrast;
		break;
	case LOMBO_CM_SAT_RED:
		ret = dc->cfg->has_cm ? dc->color.cm.sat_red : -ENODEV;
		break;
	case LOMBO_CM_SAT_ORANGE:
		ret = dc->cfg->has_cm ? dc->color.cm.sat_orange : -ENODEV;
		break;
	case LOMBO_CM_SAT_YELLOW:
		ret = dc->cfg->has_cm ? dc->color.cm.sat_yellow : -ENODEV;
		break;
	case LOMBO_CM_SAT_GREEN:
		ret = dc->cfg->has_cm ? dc->color.cm.sat_green : -ENODEV;
		break;
	case LOMBO_CM_SAT_CYAN:
		ret = dc->cfg->has_cm ? dc->color.cm.sat_cyan : -ENODEV;
		break;
	case LOMBO_CM_SAT_BLUE:
		ret = dc->cfg->has_cm ? dc->color.cm.sat_blue : -ENODEV;
		break;
	case LOMBO_CM_SAT_PURPLE:
		ret = dc->cfg->has_cm ? dc->color.cm.sat_purple : -ENODEV;
		break;
	case LOMBO_CM_SAT_MAGENTA:
		ret = dc->cfg->has_cm ? dc->color.cm.sat_magenta : -ENODEV;
		break;
	default:
		ret = -EINVAL;
		break;
	}


	return ret;
}

int lombo_dc_set_color_status(struct lombo_dc *dc, int type, int value)
{
	int ret = 0;

	switch (type) {
	case LOMBO_COLOR_ENABLE:
		if (value != dc->color.color_enable) {
			dc->color.color_enable = value ? 1 : 0;
			dc->color.ce.enhance_enable = value ? 1 : 0;
			dc->color.ce_info_changed = true;
		}
		break;
	case LOMBO_CE_BRIGHTNESS:
		if (value != dc->color.ce.brightness) {
			dc->color.ce.brightness = value;
			dc->color.ce_info_changed = true;
		}
		break;
	case LOMBO_CE_SATURATION:
		if (!dc->cfg->has_cm) {
			if (value != dc->color.ce.saturation) {
				dc->color.ce.saturation = value;
				dc->color.ce_info_changed = true;
			}
		} else {
			ret = -ENODEV;
		}
		break;
	case LOMBO_CE_CONTRAST:
		if (value != dc->color.ce.contrast) {
			dc->color.ce.contrast = value;
			dc->color.ce_info_changed = true;
		}
		break;
	case LOMBO_CM_SAT_RED:
		if (dc->cfg->has_cm) {
			if (value != dc->color.cm.sat_red) {
				dc->color.cm.sat_red = value;
				dc->color.cm_curve_changed = true;
			}
		} else {
			ret = -ENODEV;
		}
		break;
	case LOMBO_CM_SAT_ORANGE:
		if (dc->cfg->has_cm) {
			if (value != dc->color.cm.sat_orange) {
				dc->color.cm.sat_orange = value;
				dc->color.cm_curve_changed = true;
			}
		} else {
			ret = -ENODEV;
		}
		break;
	case LOMBO_CM_SAT_YELLOW:
		if (dc->cfg->has_cm) {
			if (value != dc->color.cm.sat_yellow) {
				dc->color.cm.sat_yellow = value;
				dc->color.cm_curve_changed = true;
			}
		} else {
			ret = -ENODEV;
		}
		break;
	case LOMBO_CM_SAT_GREEN:
		if (dc->cfg->has_cm) {
			if (value != dc->color.cm.sat_green) {
				dc->color.cm.sat_green = value;
				dc->color.cm_curve_changed = true;
			}
		} else {
			ret = -ENODEV;
		}
		break;
	case LOMBO_CM_SAT_CYAN:
		if (dc->cfg->has_cm) {
			if (value != dc->color.cm.sat_cyan) {
				dc->color.cm.sat_cyan = value;
				dc->color.cm_curve_changed = true;
			}
		} else {
			ret = -ENODEV;
		}
		break;
	case LOMBO_CM_SAT_BLUE:
		if (dc->cfg->has_cm) {
			if (value != dc->color.cm.sat_blue) {
				dc->color.cm.sat_blue = value;
				dc->color.cm_curve_changed = true;
			}
		} else {
			ret = -ENODEV;
		}
		break;
	case LOMBO_CM_SAT_PURPLE:
		if (dc->cfg->has_cm) {
			if (value != dc->color.cm.sat_purple) {
				dc->color.cm.sat_purple = value;
				dc->color.cm_curve_changed = true;
			}
		} else {
			ret = -ENODEV;
		}
		break;
	case LOMBO_CM_SAT_MAGENTA:
		if (dc->cfg->has_cm) {
			if (value != dc->color.cm.sat_magenta) {
				dc->color.cm.sat_magenta = value;
				dc->color.cm_curve_changed = true;
			}
		} else {
			ret = -ENODEV;
		}
		break;
	default:
		ret = -EINVAL;
		break;
	}

	return ret;
}

static int lombo_parse_color_enhance(struct lombo_dc *dc)
{
	dc_enhance_info_t *ce = &dc->color.ce;
	struct device_node *np = dc->dev->of_node;
	static const char * const prop_name[] = {
		"ce_brightness",
		"ce_saturation",
		"ce_contrast",
	};
	uint32_t i, value = 0, prop_value[3] = {0};

	for (i = 0; i < ARRAY_SIZE(prop_name); i++) {
		if (!of_property_read_u32(np, prop_name[i], &value)) {
			if (value > DC_COLOR_ENHANCE_MAX_VALUE) {
				DRM_ERROR("max %s only support %d\n",
					prop_name[i],
					DC_COLOR_ENHANCE_MAX_VALUE);
				value = DC_COLOR_ENHANCE_MAX_VALUE;
			}
			prop_value[i] = value;
			ce->enhance_enable = true;
		} else {
			prop_value[i] = DC_COLOR_ENHANCE_MAX_VALUE / 2;
		}
		DRM_DEBUG_DRIVER("prop_value[%u]=%u", i, prop_value[i]);
	}

	ce->fmt = DPU_FMT_RGB888;
	ce->range = DPU_DATA_FULL_RANGE;
	ce->csstd = DPU_CS_BT601;
	ce->brightness = prop_value[0];
	ce->saturation = prop_value[1];
	ce->contrast = prop_value[2];
	dc->color.color_enable = ce->enhance_enable;

	return 0;
}

static int lombo_parse_cm_sat_curve(struct lombo_dc *dc)
{
	dc_cm_info_t *cm = &dc->color.cm;
	struct device_node *np = dc->dev->of_node;
	static const char * const prop_name[] = {
		"sat_red",
		"sat_orange",
		"sat_yellow",
		"sat_green",
		"sat_cyan",
		"sat_blue",
		"sat_purple",
		"sat_magenta",
	};
	uint32_t i, value = 0, prop_value[8] = {0};

	for (i = 0; i < ARRAY_SIZE(prop_name); i++) {
		if (!of_property_read_u32(np, prop_name[i], &value)) {
			if (value > DC_CM_MAX_SAT) {
				DRM_ERROR("max %s only support %d\n",
					prop_name[i], DC_CM_MAX_SAT);
				value = DC_CM_MAX_SAT;
			}
			prop_value[i] = value;
			dc->color.color_enable = true;
		} else {
			prop_value[i] = DC_CM_MAX_SAT / 2;
		}
		DRM_DEBUG_DRIVER("prop_value[%u]=%u", i, prop_value[i]);
	}
	cm->sat_red = prop_value[0];
	cm->sat_orange = prop_value[1];
	cm->sat_yellow = prop_value[2];
	cm->sat_green = prop_value[3];
	cm->sat_cyan = prop_value[4];
	cm->sat_blue = prop_value[5];
	cm->sat_purple = prop_value[6];
	cm->sat_magenta = prop_value[7];

	/* use CM to adjust saturation prior than CE */
	/* ce.saturation = DC_COLOR_ENHANCE_MAX_VALUE / 2 means that */
	/* CE neither enhance nor attenuate the saturation */
	dc->color.ce.saturation = DC_COLOR_ENHANCE_MAX_VALUE / 2;

	/* map range [0,100] to [-100,100] */
	csp_dc_cm_generate_saturation_curve(
		cm->cm_curve,
		2 * prop_value[0] - 100,
		2 * prop_value[1] - 100,
		2 * prop_value[2] - 100,
		2 * prop_value[3] - 100,
		2 * prop_value[4] - 100,
		2 * prop_value[5] - 100,
		2 * prop_value[6] - 100,
		2 * prop_value[7] - 100);

	return 0;
}

static inline int fb_contain_rect(const u32 fb_w, const u32 fb_h,
	const struct drm_lombo_rect_wh *r)
{
	if (r->x < 0 ||
	    r->y < 0 ||
	    r->w == 0 ||
	    r->h == 0 ||
	    r->w > fb_w ||
	    r->x > fb_w - r->w ||
	    r->h > fb_h ||
	    r->y > fb_h - r->h) {
		return 0;
	}
	return 1;
}

static int fb_cut(const u32 fb_w, const u32 fb_h,
	const struct drm_lombo_rect_wh *trp,
	struct drm_lombo_rect_wh *res, u32 *num)
{
	int cnt = 0;

	if (!fb_contain_rect(fb_w, fb_h, trp)) {
		DRM_ERROR("fb not contain rect!\n");
		return -EINVAL;
	}

	if (*num == cnt)
		return 0;
	if (trp->y > 0) {
		res[cnt].x = 0;
		res[cnt].y = 0;
		res[cnt].w = fb_w;
		res[cnt].h = trp->y;
		cnt++;
	}

	if (*num == cnt)
		return 0;
	if (trp->x > 0) {
		res[cnt].x = 0;
		res[cnt].y = trp->y;
		res[cnt].w = trp->x;
		res[cnt].h = trp->h;
		cnt++;
	}

	if (*num == cnt)
		return 0;
	if (trp->x + trp->w < fb_w) {
		res[cnt].x = trp->x + trp->w;
		res[cnt].y = trp->y;
		res[cnt].w = fb_w - trp->x - trp->w;
		res[cnt].h = trp->h;
		cnt++;
	}

	if (*num == cnt)
		return 0;
	if (trp->y + trp->h < fb_h) {
		res[cnt].x = 0;
		res[cnt].y = trp->y + trp->h;
		res[cnt].w = fb_w;
		res[cnt].h = fb_h - trp->y - trp->h;
		cnt++;
	}

	*num = cnt;

	return 0;
}

static struct lombo_dc_win *lombo_dc_find_primary_win(
	struct lombo_dc *dc, struct drm_framebuffer *fb)
{
	struct drm_plane *plane;
	int i;

	for (i = 0; i < dc->cfg->win_num; i++) {
		plane = dc->planes[i];
		if (plane && plane->state->fb && (plane->state->fb == fb))
			return to_lombo_dc_win(plane);
	}

	DRM_ERROR("%s: no primary win\n", __func__);
	return NULL;
}

int lombo_dc_set_fb_transparent_rect(struct lombo_dc *dc,
	struct drm_framebuffer *fb, struct drm_lombo_rect_wh *rect)
{
	struct lombo_dc_win *win;
	uint32_t height;
	int ret = 0;
	int num = 0;
	int i;

	if (!dc || !fb)
		return -EINVAL;

	win = lombo_dc_find_primary_win(dc, fb);
	if (!win)
		return -EINVAL;

	height = (fb->height <= dc->scn_h) ? fb->height : dc->scn_h;
	num = ARRAY_SIZE(win->opaque);
	ret = fb_cut(fb->width, height, rect, win->opaque, &num);

	if (ret == -EINVAL) {
		win->opaque_num = -1;
		memset(win->opaque, 0, sizeof(win->opaque));
		memset(&win->trp, 0, sizeof(win->trp));
		return 0;
	} else if (ret) {
		DRM_ERROR("fb cut fail, ret=%d\n", ret);
		return ret;
	}

	win->opaque_num = num;
	memcpy(&win->trp, rect, sizeof(win->trp));
	DRM_DEBUG_KMS("win%d, trp[%d,%d,%d,%d] in fb[%d,%d]\n", win->id,
		rect->x, rect->y, rect->w, rect->h, fb->width, height);
	for (i = 0; i < num; i++) {
		rect = &win->opaque[i];
		DRM_DEBUG_KMS("opaque(%d)[%d,%d,%d,%d]\n",
			i, rect->x, rect->y, rect->w, rect->h);
	}

	return ret;
}
int lombo_dc_get_fb_transparent_rect(struct lombo_dc *dc,
	struct drm_framebuffer *fb, struct drm_lombo_rect_wh *rect)
{
	struct lombo_dc_win *win;

	if (!dc || !fb)
		return -EINVAL;

	win = lombo_dc_find_primary_win(dc, fb);
	if (!win)
		return -EINVAL;

	memcpy(rect, &win->trp, sizeof(*rect));
	return 0;
}

static int lombo_dc_pm_notifier(struct notifier_block *nb, unsigned long val,
			      void *ptr)
{
	struct lombo_dc *dc = container_of(nb, struct lombo_dc, nb);
	int i;

	switch (val) {
	case PM_SUSPEND_PREPARE:
		for (i = 0; i < dc->cfg->win_num; i++) {
			struct lombo_dc_win *win =
				to_lombo_dc_win(dc->planes[i]);

			if (!win->se)
				continue;
			win->se_disable = 1;
			win->se_commit = 1;
		}
		dc->sleep = true;
		lombo_dc_commit(dc);
		break;
	case PM_POST_SUSPEND:
		dc->sleep = false;
		break;
	default:
		break;
	}

	return 0;
}

static int lombo_dc_bind(struct device *dev,
	struct device *master, void *master_data)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct drm_device *drm = master_data;
	struct lombo_drv *drv = drm->dev_private;
	struct lombo_dc *dc = NULL;
	uint32_t value = 0;
	struct resource *res = NULL;

	DRM_DEV_DEBUG_DRIVER(dev, "\n");

	if (!drv->dpu_top) {
		DRM_DEV_ERROR(dev, "null dpu_top\n");
		return -EINVAL;
	}

	dc = devm_kzalloc(dev, sizeof(*dc), GFP_KERNEL);
	if (!dc) {
		DRM_DEV_ERROR(dev, "alloc for dc failed.\n");
		return -ENOMEM;
	}
	dc->dev = dev;
	dc->drm = drm;
	dev_set_drvdata(dev, dc);

	/* get hw cfg */
	dc->cfg = of_device_get_match_data(dev);
	if (!dc->cfg) {
		DRM_DEV_ERROR(dev, "get match data failed.\n");
		return -EINVAL;
	}

	/* get dc id */
	value = lombo_dev_get_id(dev, "-dc-");
	if (value >= DC_NUM) {
		DRM_DEV_ERROR(dev, "inval dc_id=%d\n", value);
		return -EINVAL;
	}
	dc->id = (uint8_t)value;

	/* parse register */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	dc->reg = devm_ioremap_resource(dev, res);
	if (IS_ERR(dc->reg))
		return PTR_ERR(dc->reg);
	csp_dc_set_register_base(dc->id,
				 (unsigned long)dc->reg,
				 (u32)resource_size(res));

	/* init clock */
	dc->clk = lombo_dpu_get_clk_def(drv->dpu_top, dev,
					LOMBO_DPU_MOD_DC0 + dc->id);
	if (!dc->clk) {
		DRM_DEV_ERROR(dev, "dpu get clk for dc fail\n");
		return -EINVAL;
	}

	/* parse color enhance */
	lombo_parse_color_enhance(dc);
	/* parse color management */
	if (dc->cfg->has_cm)
		lombo_parse_cm_sat_curve(dc);

	INIT_WORK(&dc->page_flip_work, lombo_dc_page_flip_worker);

	dc->user_blk = of_property_read_bool(dev->of_node, "user_blk");

	list_add_tail(&dc->list, &dc_list);

	/* lombo_crtc_init(drm, dc, NULL); just for debug */
	dc->nb.notifier_call = lombo_dc_pm_notifier;
	register_pm_notifier(&dc->nb);

	return 0;
}

static void lombo_dc_unbind(struct device *dev,
	struct device *master, void *master_data)
{
	struct lombo_dc *dc = dev_get_drvdata(dev);

	DRM_DEV_DEBUG_DRIVER(dev, "\n");

	unregister_pm_notifier(&dc->nb);
	list_del(&dc->list);
	lombo_dpu_put_clk(&dc->clk);
	csp_dc_set_register_base(dc->id, 0, 0);
}

static const struct component_ops dc_comp_ops = {
	.bind	= lombo_dc_bind,
	.unbind	= lombo_dc_unbind,
};

static int lombo_dc_probe(struct platform_device *pdev)
{
	DRM_DEV_DEBUG_DRIVER(&pdev->dev, "%d\n", __LINE__);
	return component_add(&pdev->dev, &dc_comp_ops);
}

static int lombo_dc_remove(struct platform_device *pdev)
{
	DRM_DEV_DEBUG_DRIVER(&pdev->dev, "%d\n", __LINE__);
	component_del(&pdev->dev, &dc_comp_ops);
	return 0;
}

static const struct lombo_dc_cfg n5v1_dc0_cfg = {
	.win_num = 2,
	.has_cm = 1,
	.no_hw_cursor = 1,
	.no_ifb = 1,
};

static const struct lombo_dc_cfg n7v1_dc0_cfg = {
	.win_num = 4,
	.no_win_sc = 1,
};

static const struct lombo_dc_cfg n7v3_dc0_cfg = {
	.win_num = 4,
	.has_cm = 1,
};

static const struct lombo_dc_cfg n7v5_dc0_cfg = {
	.win_num = 2,
	.has_cm = 1,
	.no_hw_cursor = 1,
	.no_ifb = 1,
};

static const struct lombo_dc_cfg n9v1_dc0_cfg = {
	.win_num = 4,
	.has_se_hmir = 1,
};

static const struct lombo_dc_cfg n9v1_dc1_cfg = {
	.win_num = 2,
	.no_hw_cursor = 1,
	.no_ifb = 1,
};

static const struct of_device_id dc_of_table[] = {
	{
		.compatible = "lombo,lombo-n5v1-dc-0",
		.data = &n5v1_dc0_cfg,
	}, {
		.compatible = "lombo,lombo-n7v1-dc-0",
		.data = &n7v1_dc0_cfg,
	}, {
		.compatible = "lombo,lombo-n7v3-dc-0",
		.data = &n7v3_dc0_cfg,
	}, {
		.compatible = "lombo,lombo-n7v5-dc-0",
		.data = &n7v5_dc0_cfg,
	}, {
		.compatible = "lombo,lombo-n9v1-dc-0",
		.data = &n9v1_dc0_cfg,
	}, {
		.compatible = "lombo,lombo-n9v1-dc-1",
		.data = &n9v1_dc1_cfg,
	}
};
MODULE_DEVICE_TABLE(of, dc_of_table);

static struct platform_driver lombo_dc_platform_driver = {
	.probe		= lombo_dc_probe,
	.remove		= lombo_dc_remove,
	.driver		= {
		.owner = THIS_MODULE,
		.name		= "lombo-dc",
		.of_match_table	= dc_of_table,
	},
};
module_platform_driver(lombo_dc_platform_driver);

MODULE_AUTHOR("lomboswer <lomboswer@lombotech.com>");
MODULE_DESCRIPTION("Lombo SoC DPU DC Driver");
MODULE_LICENSE("GPL");
